package com.suduku.calc;

import com.suduku.entity.Box;
import com.suduku.util.SudoUtil;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 数链-数块法（如果某列中，数字n只有3个空白格B1,B2,B3，并且其中有两个在同一个宫内，另外一个在不同宫内，
 * 同时存在另外一个列中，数字n只有两个空白格B4,B5，且不在同一宫内，并且不在B1B2B3宫内。
 * 并且满足B1,B2,B3与B4,B5能够形成矩形。
 * 则在有两个空白格的宫内与矩形边共同作用的区域，不能有数字n） <br/>
 *
 * 测试数据：DataConstant.OTHER_SU_LIAN_K_01
 * 测试数据：SudoUtil.transpose(DataConstant.OTHER_SU_LIAN_K_01)
 */
public class SuLianKuaiCalc extends AbstractCalc {

    /**
     * 1. 遍历所有数字
     * 2. 遍历所有行，找出数字n的空白格，并且只有3个还满足 B1B2|B3 或 B1|B2B3的分布条件
     * 3. 再次遍历所有行，找出数字n的空白格，且只有2个，还满足跟第一列不在同一宫，且两个空白格也不在同一宫
     * 4. 校验是否其中有4个点形成矩形
     * 5. 剔除单元格
     */
    @Override
    Box solve() {
        for(Integer n : Box.INIT_LIST) {
            Box clearBox = findAndClear(getYentries(), n, Box::getX);
            if (clearBox != null) {
                return clearBox;
            }
            clearBox = findAndClear(getXentries(), n, Box::getY);
            if (clearBox != null) {
                return clearBox;
            }
        }
        return null;
    }

    /**
     * 功能描述: 查找并清理 <br/>
     *
     * @param entries 遍历的区域，行或列
     * @param n 候选值
     * @param getFun 方法
     * @return "com.suduku.entity.Box"
     */
    private Box findAndClear(Set<Map.Entry<Integer, List<Box>>> entries, Integer n, Function<Box, Integer> getFun) {
        // 遍历所有列，获取数字n所在的列只有3个空白格，并且其中有两个空白格在同一宫内
        for (Map.Entry<Integer, List<Box>> l1Entry : entries) {
            List<Box> b1b2b3List = l1Entry.getValue().stream()
                    .filter(b -> b.isBlank() && b.getCList().contains(n))
                    .collect(Collectors.toList());
            // 判断是否有两个空白格在同一宫内，另外一个空白格不在该宫内
            if(SudoUtil.isTwoBoxAndOne(b1b2b3List)) {
                for (Map.Entry<Integer, List<Box>> l2Entry : entries) {
                    // 再查找出只有两个空白格的列
                    List<Box> b4b5List = l2Entry.getValue().stream()
                            .filter(b -> b.isBlank() && b.getCList().contains(n))
                            .collect(Collectors.toList());
                    if (SudoUtil.isTwoBox(b4b5List) && b1b2b3List.get(0).getG() != b4b5List.get(0).getG()) {
                        // 形成矩形，并清理单元格
                        Box clearBox = findAndClear(b1b2b3List.get(0), b1b2b3List.get(1), b1b2b3List.get(2), b4b5List, n, getFun);
                        if (clearBox != null) {
                            return clearBox;
                        }
                        clearBox = findAndClear(b1b2b3List.get(1), b1b2b3List.get(0), b1b2b3List.get(2), b4b5List, n, getFun);
                        if (clearBox != null) {
                            return clearBox;
                        }
                        clearBox = findAndClear(b1b2b3List.get(2), b1b2b3List.get(0), b1b2b3List.get(1), b4b5List, n, getFun);
                        if (clearBox != null) {
                            return clearBox;
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * 功能描述: 发现矩形，并且清理单元格 <br/>
     *
     * @param b1 不在矩形内的点
     * @param b2 矩形第一个端点
     * @param b3 矩形第二个端点
     * @param b4b5List 矩形端点
     * @param n 候选值
     * @return "com.suduku.entity.Box"
     */
    private Box findAndClear(Box b1, Box b2, Box b3, List<Box> b4b5List, Integer n, Function<Box, Integer> getFun) {
        // 判断是否形成矩形
        if(getFun.apply(b2).equals(getFun.apply(b4b5List.get(0))) && getFun.apply(b3).equals(getFun.apply(b4b5List.get(1)))) {
            // 获取宫内数据
            List<Box> clearList = getGList(b1).stream()
                    .filter(b -> b.isBlank()
                            && b.getX() == (b1.getG() == b2.getG() ? getFun.apply(b2) : getFun.apply(b3))
                            && b.getI() != b2.getI() && b.getI() != b3.getI()
                            && b.getCList().contains(n))
                    .collect(Collectors.toList());
            for(Box box : clearList) {
                box.removeCList(n);
                change(box, SudoUtil.getList(b4b5List, b1, b2, b3), n);
                return box;
            }
        }
        return null;
    }

}
